<?php

declare(strict_types=1);

namespace app\service\sys;

use app\model\AuthPermission;
use app\model\AuthRole;
use app\model\AuthUserRole;
use InvalidArgumentException;
use think\Collection;
use think\facade\Db;
use think\Paginator;

class RoleService
{
    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $query = AuthRole::with([
            'permissions' => static function ($relation) {
                $relation->field(['id', 'name', 'code', 'client_type', 'status']);
            },
        ])->order('id', 'desc');

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(function ($subQuery) use ($keyword) {
                $subQuery->whereLike('name', '%' . $keyword . '%')
                    ->whereOr('slug', 'like', '%' . $keyword . '%')
                    ->whereOr('description', 'like', '%' . $keyword . '%');
            });
        }

        if (isset($filters['status']) && $filters['status'] !== '') {
            $query->where('status', (int) $filters['status']);
        }

        if (!empty($filters['scope_type'])) {
            $query->where('scope_type', (string) $filters['scope_type']);
        }

        $paginator = $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);

        $this->appendUsersCount($paginator->items());

        return $paginator;
    }

    public function findById(int $id): ?AuthRole
    {
        $role = AuthRole::with([
            'permissions' => static function ($relation) {
                $relation->field(['id', 'name', 'code', 'client_type', 'status']);
            },
            'users' => static function ($relation) {
                $relation->field(['id', 'phone', 'email', 'status']);
            },
        ])->find($id);

        if ($role) {
            $this->appendUsersCount([$role]);
        }

        return $role;
    }

    public function create(array $payload): AuthRole
    {
        $name = trim((string) ($payload['name'] ?? ''));
        $slug = trim((string) ($payload['slug'] ?? ''));
        $scope = $payload['scope_type'] ?? 'system';
        $description = trim((string) ($payload['description'] ?? ''));
        $status = isset($payload['status']) ? (int) $payload['status'] : 1;
        $permissionIds = $this->normalizeIds($payload['permission_ids'] ?? []);

        if ($name === '') {
            throw new InvalidArgumentException('角色名称不能为空');
        }

        if ($slug === '') {
            throw new InvalidArgumentException('角色标识不能为空');
        }

        $this->assertSlugUnique($slug);

        if (!in_array($scope, ['system', 'org'], true)) {
            $scope = 'system';
        }

        if (!in_array($status, [0, 1], true)) {
            $status = 1;
        }

        return Db::transaction(function () use (
            $name,
            $slug,
            $scope,
            $description,
            $status,
            $permissionIds
        ) {
            /** @var AuthRole $role */
            $role = AuthRole::create([
                'name' => $name,
                'slug' => $slug,
                'scope_type' => $scope,
                'description' => $description,
                'status' => $status,
            ]);

            $this->syncPermissions($role, $permissionIds);

            return $this->refreshRole($role);
        });
    }

    public function update(AuthRole $role, array $payload): AuthRole
    {
        $name = array_key_exists('name', $payload) ? trim((string) $payload['name']) : $role->name;
        $slug = array_key_exists('slug', $payload) ? trim((string) $payload['slug']) : $role->slug;
        $scope = array_key_exists('scope_type', $payload) ? (string) $payload['scope_type'] : $role->scope_type;
        $description = array_key_exists('description', $payload) ? trim((string) $payload['description']) : $role->description;
        $status = array_key_exists('status', $payload) ? (int) $payload['status'] : (int) $role->status;
        $permissionIds = $this->normalizeIds($payload['permission_ids'] ?? null);

        if ($name === '') {
            throw new InvalidArgumentException('角色名称不能为空');
        }

        if ($slug === '') {
            throw new InvalidArgumentException('角色标识不能为空');
        }

        if ($slug !== $role->slug) {
            $this->assertSlugUnique($slug, (int) $role->id);
        }

        if (!in_array($scope, ['system', 'org'], true)) {
            $scope = $role->scope_type;
        }

        if (!in_array($status, [0, 1], true)) {
            $status = (int) $role->status;
        }

        return Db::transaction(function () use (
            $role,
            $name,
            $slug,
            $scope,
            $description,
            $status,
            $permissionIds
        ) {
            $role->save([
                'name' => $name,
                'slug' => $slug,
                'scope_type' => $scope,
                'description' => $description,
                'status' => $status,
            ]);

            if ($permissionIds !== null) {
                $this->syncPermissions($role, $permissionIds);
            }

            return $this->refreshRole($role);
        });
    }

    public function delete(AuthRole $role): bool
    {
        $inUse = AuthUserRole::where('role_id', $role->id)->count() > 0;
        if ($inUse) {
            throw new InvalidArgumentException('角色仍被用户使用，无法删除');
        }

        return (bool) Db::transaction(function () use ($role) {
            $role->permissions()->detach();
            return $role->delete();
        });
    }

    public function format(AuthRole $role): array
    {
        $permissions = $role->permissions instanceof Collection
            ? $role->permissions
            : Collection::make($role->permissions ?? []);

        return [
            'id' => (int) $role->id,
            'name' => $role->name,
            'slug' => $role->slug,
            'scope_type' => $role->scope_type,
            'description' => $role->description,
            'status' => (int) $role->status,
            'create_time' => $role->create_time,
            'update_time' => $role->update_time,
            'users_count' => (int) ($role->getAttr('users_count') ?? 0),
            'permissions' => array_values($permissions->map(static function ($permission) {
                return [
                    'id' => (int) $permission->id,
                    'name' => $permission->name,
                    'code' => $permission->code,
                    'client_type' => $permission->client_type,
                    'status' => (int) $permission->status,
                ];
            })->toArray()),
        ];
    }

    public function formatCollection(iterable $roles): array
    {
        $results = [];
        foreach ($roles as $role) {
            if ($role instanceof AuthRole) {
                $results[] = $this->format($role);
            }
        }

        return $results;
    }

    private function syncPermissions(AuthRole $role, array $permissionIds): void
    {
        $permissionIds = array_values(array_unique(array_map('intval', $permissionIds)));
        $permissionIds = array_filter($permissionIds, static fn(int $id) => $id > 0);

        $validIds = $permissionIds === []
            ? []
            : array_values(AuthPermission::whereIn('id', $permissionIds)->column('id'));

        if (count($validIds) !== count($permissionIds)) {
            throw new InvalidArgumentException('存在无效的权限ID');
        }

        // 使用 ThinkPHP 的多对多更新能力，确保软删除记录正确处理
        $relation = $role->permissions();
        $relation->detach();

        if ($validIds !== []) {
            $relation->attach($validIds);
        }
    }

    private function assertSlugUnique(string $slug, ?int $excludeId = null): void
    {
        $query = AuthRole::where('slug', $slug);
        if ($excludeId !== null) {
            $query->where('id', '<>', $excludeId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('角色标识已存在');
        }
    }

    private function normalizeIds(mixed $ids): ?array
    {
        if ($ids === null) {
            return null;
        }

        if (!is_array($ids)) {
            throw new InvalidArgumentException('权限参数格式不正确');
        }

        $converted = array_values(array_unique(array_map('intval', $ids)));

        return array_values(array_filter($converted, static fn(int $id) => $id > 0));
    }

    private function refreshRole(AuthRole $role): AuthRole
    {
        /** @var AuthRole $fresh */
        $fresh = $this->findById((int) $role->id);
        return $fresh;
    }

    private function appendUsersCount(iterable $roles): void
    {
        $ids = [];
        foreach ($roles as $role) {
            if ($role instanceof AuthRole && $role->id !== null) {
                $ids[] = (int) $role->id;
            }
        }

        if ($ids === []) {
            return;
        }

        $rows = AuthUserRole::whereIn('role_id', $ids)
            ->whereNull('delete_time')
            ->field('role_id, COUNT(*) AS cnt')
            ->group('role_id')
            ->select()
            ->toArray();

        $counts = [];
        foreach ($rows as $row) {
            $roleId = (int) ($row['role_id'] ?? 0);
            if ($roleId > 0) {
                $counts[$roleId] = (int) ($row['cnt'] ?? 0);
            }
        }

        foreach ($roles as $role) {
            if ($role instanceof AuthRole && $role->id !== null) {
                $count = $counts[(int) $role->id] ?? 0;
                $role->setAttr('users_count', (int) $count);
            }
        }
    }
}
